<?php
/**
 * KePHP, Keep PHP easy!
 *
 * @license   https://opensource.org/licenses/MIT
 * @copyright Copyright 2015-2018 KePHP Authors All Rights Reserved
 * @link      http://kephp.com/utils ( https://git.oschina.net/kephp/kephp-utils )
 * @author    曾建凯 <janpoem@163.com>
 */

namespace Ke\TestUtils;

/**
 * 类实例辅助测试工具（Trait）
 *
 * @package Ke\TestUtils
 */
trait TestClassInstanceTrait
{
	
	use TestAnnotationHelperTrait;
	
	private $_testInstance = null;
	
	/** @var \ReflectionObject */
	private $_testInstanceRef = null;
	
	/**
	 * 设置当前测试的实例对象
	 *
	 * @param $obj
	 * @return $this
	 */
	public function setTestInstance($obj)
	{
		if (!isset($this->_testInstance) && is_object($obj)) {
			$this->_testInstance = $obj;
			$this->_testInstanceRef = new \ReflectionObject($obj);
		}
		return $this;
	}
	
	/**
	 * 生成一个实例类方法的测试对象
	 *
	 * @param string $method
	 * @return TestMethod
	 */
	public function newTestMethod(string $method): TestMethod
	{
		try {
			$refMethod = $this->_testInstanceRef->getMethod($method);
		} catch (\Throwable $throwable) {
		}
		return new TestMethod($this->_testInstance, $refMethod ?? null, $throwable ?? null);
	}
	
	/**
	 * 执行一个方法进行测试
	 *
	 * 该方法不关心具体的测试内容，实质的测试内容，由testItemCallback来实现，所以未传入这个参数时，会抛出一个异常。
	 *
	 * @param string|TestMethod $method             要进行测试的方法，可以是字符串，也可以是一个 TestMethod 实例
	 * @param array             $items              要进行单元测试的测试用例
	 * @param callable|null     $filterItemCallback 每个测试用例实例化时的回调过滤函数
	 * @param callable|null     $testItemCallback   每个测试用例实例实际执行测试的回调函数
	 * @return \Ke\TestUtils\TestMethod
	 */
	public function runMethodTest($method, array $items, $filterItemCallback = null, $testItemCallback = null)
	{
		if (is_string($method))
			$method = $this->newTestMethod($method);
		
		if (!($method instanceof TestMethod))
			$method = new TestMethod($this->_testInstance, null, new TestMethodException('Invalid test method!'));
		
		try {
			if (!isset($testItemCallback))
				throw new TestMethodException('Undefined testItemCallback!');
			
			if (!is_callable($testItemCallback))
				throw new TestMethodException('testItemCallback is not callable!');
			
			if (empty($method))
				throw new TestMethodException('Unspecified test method!');
			
			$testItems = $method->newTestItems($items, $filterItemCallback);
			
			if (empty($testItems))
				throw new TestMethodException('Test items is empty!');
			
			/** @var TestItem $item */
			foreach ($testItems as $index => $item) {
				$testItemCallback($this->_testInstance, $index, $item);
			}
		} catch (\Throwable $throwable) {
			$method->addException($throwable);
		}
		
		return $method;
	}
}